iT邦幫忙

2024 iThome 鐵人賽

DAY 9
0

型別檔輸出設定 ( sanity-typegen.json )

前一篇講那麼久,都是輸出型別檔後,再手動複製型別檔到其他專案內,或是輸出的檔名固定是 sanity.types.ts
但是不管是輸出路徑還是檔名,都是可以修改的。

只要在 Sanity 專案內建立 sanity-typegen.json ,並且修改裡面的設定就可以了。

{
  "path": "./src/**/*.{ts,tsx,js,jsx}",
  "schema": "schema.json",
  "generates": "./sanity.types.ts", // <- 輸出路徑跟檔名
  "overloadClientMethods": true
}

只要修改 generates 就可以定義要輸出的型別檔位置了,

.
├── next-app
│   ├── app
│   │   └── sanity
└── sanity-project
    ├── sanity-typegen.json // <- Sanity 輸出設定檔
    ├── sanity.types.ts
    ├── schema.json

舉例來說,我現在要把從 sanity-project 專案內生成出來的型別檔輸出到 next-app 內,並且存在 /next-app/app/sanity/ 資料夾下,檔名為 types.ts

我可以在 sanity-typegen.json 內這樣改:

{
  "path": "./src/**/*.{ts,tsx,js,jsx}",
  "schema": "schema.json",
  "generates": "../next-app/app/sanity/types.ts", // 輸出路徑跟檔名
  "overloadClientMethods": true
}

這樣就完成設定了!
使用上可以這樣使用:

next-app
├── app
│   ├── page.tsx
│   └── sanity
│       └── types.ts // <- 輸出的型別檔
// page.tsx 使用範例
import { client } from "@/app/sanity/lib/client";
import { BLOG_POSTS_QUERY } from "@/app/sanity/lib/queries";
import type { BlogPost } from "./sanity/types";

export default async function Home() {
  const posts = await client.fetch<BlogPost[]>(BLOG_POSTS_QUERY);
  return (
    <ul className="home-page">
      {/* 型別就沒錯誤了 */}
      {posts.map((post) => (
        <li key={post._id}>
          <a href={`/posts/${post?.slug}`}>{post?.title}</a>
        </li>
      ))}
    </ul>
  );
}

但是先別急著拿去用,還有再更進階的,產生的型別甚至可以根據 defineQuery 包裹的 GROQ 語法去自動產生語法型別的回傳。

什麼意思?
意思是 sanity typegen 會根據

import { defineQuery } from "next-sanity";

export const BLOG_POSTS_QUERY = defineQuery(`*[_type == "blogPost"]`);

自動在使用 sanity client 呼叫語法時回傳相對應的型別。

實際要讓這個 feature 生效,則是要確保在 sanity-typegen.json 中的 overloadClientMethodstrue,並且定義的 path 有包含到 defineQuery 的的檔案。

回到我們的案例:

.
├── next-app
│   ├── app
│   │   └── sanity
│   │       ├── lib // <- query 語法都寫在這
│   │       └── types.ts
└── sanity-project
    ├── sanity-typegen.json

我們的案例是,所有 Next.js 專案的 GROQ 語法都會寫在 next-app/app/sanity/lib 內,並且都是以 .ts 檔案的形式,所以我可以修改 sanity-typegen.json 中的 path 為:

{
  "path": "../next-app/app/sanity/lib/*.ts", // <- 專案內 "defineQuery" 都集中在這管理
  "schema": "schema.json",
  "generates": "../next-app/app/sanity/types.ts",
  "overloadClientMethods": true // <- 此欄位一定要是 true,才會有這項 feature
}

設定完成後再使用指令產生型別檔就完成了。
( 因為沒有動到 schema 結構,就不用再產生一次 schema 了 )

sanity typegen generate

這麼一來在使用 client 使用 fetch() 方法時,型別就會自動推斷而不用再去引入了:

import { client } from "@/app/sanity/lib/client";
import { BLOG_POSTS_QUERY } from "@/app/sanity/lib/queries";

export default async function Home() {
  const posts = await client.fetch(BLOG_POSTS_QUERY);
  return (
    <ul className="home-page">
      {posts.map((post) => (
        <li key={post._id}>
          <a href={`/posts/${post?.slug}`}>{post?.title}</a>
        </li>
      ))}
    </ul>
  );
}

連型別都不用引入了,直接就有型別的推斷了!很舒服吧~~

那在 type.ts ( 舊稱 sanity.types.ts ) 型別檔根據此參數產生的細節在這裡,看看就不特別解釋這段了:

// Source: ../next-app/app/sanity/lib/queries.ts
// Variable: BLOG_POSTS_QUERY
// Query: *[_type == "blogPost"]
export type BLOG_POSTS_QUERYResult = Array<{
  _id: string;
  _type: "blogPost";
  _createdAt: string;
  _updatedAt: string;
  _rev: string;
  title: string;
  slug: string;
  subtitle?: string;
  heroImage: {
    asset?: {
      _ref: string;
      _type: "reference";
      _weak?: boolean;
      [internalGroqTypeReferenceTo]?: "sanity.imageAsset";
    };
    hotspot?: SanityImageHotspot;
    crop?: SanityImageCrop;
    _type: "image";
  };
  content: string;
  publishedAt: string;
  tags?: Array<string>;
}>;

// Query TypeMap
import "@sanity/client";
declare module "@sanity/client" {
  interface SanityQueries {
    "*[_type == \"blogPost\"]": BLOG_POSTS_QUERYResult;
  }
}

上一篇
Day 8 - Sanity 產生 TypeScript 型別檔
下一篇
Day 10 - Sanity GROQ 語法入門
系列文
用 Sanity 跟 Nextjs 重寫個人部落格30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言